#include "clib_tuple_hash.h"
#include "clib_stack_malloc.h"
#include "clib_sock.h"
#include "clib_net_def.h"

clib_tuple_table_t* 
clib_tuple_hash_table_create(hash_fn hash,equal_fn eqfn,u32_t bins,u32_t is_lk,i32_t socket_id)
{
    hash_node_t *node;
    clib_tuple_table_t* table = clib_stack_malloc(                                       \
                                 NULL                                                    \
                                ,sizeof(clib_tuple_table_t) + sizeof(hash_node_t) * bins \
                                ,CLIB_ALIGN_SIZE
                                ,socket_id);
    if(!table) {
        return NULL;
    }
    table->bins = bins;
    table->hash = hash;
    table->eqfn = eqfn;
    table->need_lk = is_lk;
    for(int i = 0; i < bins; i++) {
        node = &table->node[i];
        clib_spinlock_init(&node->lock);
        clib_queue_init(&node->list);
    }

    return table;
}

void 
clib_tuple_hash_table_destroy(clib_tuple_table_t *ht)
{
    if(ht)
    {
        clib_stack_free(ht);
    }
}

void*
clib_tuple_hash_table_search(clib_tuple_table_t *ht, void *key, i32_t ipv4)
{
    u32_t idx;
    hash_node_t  *node;
    clib_queue_t *next;
    clib_sock_t  *sock = NULL;
    if(ipv4) {
        idx = ht->hash(key,16);
    } else {
        idx = ht->hash(key,40);
    }
    node = &ht->node[idx];
    if(ht->need_lk)
    {
        clib_spinlock_lock(&node->lock);
    }

    for(next = clib_queue_next(&node->list); 
        next != clib_queue_sentinel(next); 
        next = clib_queue_next(next))
    {
        if(sock->ipv4 == ipv4)
        {
            sock = clib_queue_data(next,clib_sock_t,list);
            if(ipv4) {
                if(ht->eqfn(key,&sock->ipv4_tuple,16)) {
                    break;
                }
            } else {
                if(ht->eqfn(key,&sock->ipv6_tuple,40)) {
                    break;
                }
            }
        }
        sock = NULL;
    }
    
    if(ht->need_lk)
    {
        clib_spinlock_unlock(&node->lock);
    }

    return sock;
}

u32_t
clib_tuple_hash_table_remove(clib_tuple_table_t *ht,clib_sock_t *sock)
{
    u32_t idx;
    hash_node_t  *node;
    clib_queue_t *next;

    if(!ht->need_lk)
    {
        clib_queue_remove(&sock->tuple_list);
        return rn_ok;
    }
    
    if(sock->ipv4) {
        idx = ht->hash(&sock->ipv4_tuple,16);
    } else {
        idx = ht->hash(&sock->ipv6_tuple,40);
    }
    node = &ht->node[idx];
    
    clib_spinlock_lock(&node->lock);

    clib_queue_remove(&sock->tuple_list);

    clib_spinlock_unlock(&node->lock);

    return rn_ok;
}

u32_t
clib_tuple_hash_table_insert(clib_tuple_table_t*ht,clib_sock_t *sock)
{
    u32_t idx;
    hash_node_t  *node;
    clib_queue_t *next;
    clib_sock_t  *eq_sock = NULL;

    if(sock->ipv4) {
        idx = ht->hash(&sock->ipv4_tuple,16);
    } else {
        idx = ht->hash(&sock->ipv6_tuple,40);
    }
    
    node = &ht->node[idx];
    if(ht->need_lk)
    {
        clib_spinlock_lock(&node->lock);
    }

    clib_queue_insert_tail(&node->list,&sock->tuple_list);

    if(ht->need_lk)
    {
        clib_spinlock_unlock(&node->lock);
    }
    return rn_ok;
}

u32_t
clib_tuple_jshash(void *key,i32_t len)
{
    u32_t crc = 1315423911;

    u08_t *data = key;

    for(i32_t i = 0; i < len; i += 4) {
        crc ^= ((crc << 5) + (data[i]) + (crc >> 2));
        crc ^= ((crc << 5) + (data[i + 1]) + (crc >> 2));
        crc ^= ((crc << 5) + (data[i + 2]) + (crc >> 2));
        crc ^= ((crc << 5) + (data[i + 3]) + (crc >> 2));
    }

    return crc;
}

i32_t
clib_tuple_equal(const void *key1, const void *key2, i32_t len)
{

    u64_t *a = key1;
    u64_t *b = key2;

    if(len == 16) {
        if(a[0] == b[0] && a[1] == b[1]) {
            return 0;
        }
    } else {
        if(  a[0] == b[0] 
          && a[1] == b[1]
          && a[2] == b[2]
          && a[3] == b[3]
          && a[4] == b[4] ) {
            return 0;
        }
    }

    return -1;
}